/*
 * 
 * more: http://code.google.com/p/jstal/
 * 
 */
 
var JSTAL = function(template){
	this.globalScope = this;
	this.talNS = new Namespace('tal',"http://xml.zope.org/namespaces/tal");
	this.metalNS = new Namespace('metal',"http://xml.zope.org/namespaces/metal");
	this.loadTpl = function( template ){
		var addNamespaces = function( string ){
			if( !(/xmlns:tal/.test(string))){
				string = string.replace(/(^<[^\s>]*)/, "$1 xmlns:tal='http://xml.zope.org/namespaces/tal' ");
			}
			if( !(/xmlns:metal/.test(string))){
				string = string.replace(/(^<[^\s>]*)/, "$1 xmlns:metal='http://xml.zope.org/namespaces/metal' ");
			}
			try {
				return XML(string);
			} catch(e) {
				throw new Error( e );
			}
		}
		return (typeof template == 'xml') ? template : (typeof template == 'string') ? (addNamespaces(template)) : (XML('<></>'));;
	}
	
	this.tpl = this.loadTpl( template );
	this.macros = {};
	
	this.tales = function(args){
		args = args.split('|');
		args = (args instanceof Array) ? args : [args];
		for(var i=0,j=args.length;i<j;i++){
			var a = args[i];
			if(typeof a=='string' && a.match(/:/)){
				if(a.match(/(\$\{)(.*)(\})/)){
					try {
						var t = eval(a.replace(/.*(\$\{)(.*)(\}).*/gi, "$2"))
						a = a.replace(/(\$\{)(.*)(\})/gi, t);
					} catch(e){}
				}
				if(a.match(/^string:/gi)){
					return a.replace(/string:\s*/i,'');
				}
				if(a.match(/^not:/gi)){
					a = a.replace(/not:\s*/i,'!');
				}
				if(a.match(/^path:/i)){
					try {
						a = eval(a.replace(/path:\s*/i,'')) 
						return a;
					} catch(e){}
				}
				if(a.match(/^!?exists:/gi)){
					try {
						a = a.replace(/(^exists:\s*)(.*)/i,"js: typeof $2 != 'undefined'");
						a = a.replace(/(^!exists:\s*)(.*)/i,"js: typeof $2 == 'undefined'");
					} catch(e){ }
				}
				if(a.match(/^!?js:/)){
					try {
						a = eval(a.replace(/js:\s*/,''));
						return a;
					} catch(e){ }
				}
			} else if(a!='default') {
				try { 
					a = eval(a); 
					if(a===0)return "0";
					else if(a)return a;
				}
				catch(e){}
			} else {
				return a;
			}
		}
	}
	this.getAttr = function(node, talattr){
		var tal = this.talNS;
		var attrValue = (typeof node.@tal::[talattr].toString()=='string')  ? 
			node.@tal::[talattr].toString().replace(/^\s/,'').replace(/\//gi,'.').replace(/\s*\|\s*/g,'|').replace(/\s+/g,' ') : false;
		delete node.@tal::[talattr];
		return attrValue;
	}
	this.getMetalAttr = function(node, talattr){
		var tal = this.metalNS;
		var attrValue = (typeof node.@tal::[talattr].toString()=='string')  ? 
			node.@tal::[talattr].toString().replace(/^\s/,'').replace(/\s+/g,' ') : false;
		delete node.@tal::[talattr];
		return attrValue;
	}

	this.eachNode = function(fn,contextnode){
    	contextnode = contextnode || this.tpl;
    	fn.call(this,contextnode);
    	for each(i in contextnode..*){
			try {
				fn.call(this,i);
			} 
			catch(e){}
		}
	}
	var nothing = ' ';
	
	this.emptyHtmlElements = ['br','hr','img','input'];
	
	return this;
}

JSTAL.prototype = {
	execute : function(){
		with(this){
			eachNode( function( node, a){
    			usemacro( node );
			});
			eachNode(function(node,a){
	    		define(node);
				repeat(node);
			});
			eachNode(function(node,a){
				condition(node);
				content(node);
				replace(node);
				attributes(node);
			});
			eachNode(function(node,a){
				omit(node);
				emptynodes(node);
			});
			XML.prettyIndent = 0;
			return tpl.toXMLString().replace(/xmlns.*tal[^\s>]+/g, '').replace("'", '&#039;');
		}
	},
	
	defineGlobal : function(object) {
      this.globalScope = object;
      return this;
    },
    
	attributes : function(node,a){
		if(a=this.getAttr(node,'attributes')){
			a = a.split(';')
			a = (a instanceof Array) ? (a.reverse()) : [a]
			for(var i=a.length-1, j=0;i>=j;i--){
				var atr=a[i].replace(/^\s/,'').split(' ');
				node.@[atr.shift()] = this.tales(atr.join(' ')) || ''
			}
		}
	},
	
    content: function(node,a) {
		if(a=this.getAttr(node,'content')){
			a=this.tales(a);
			(a!='default')&&(delete node..* && (a&&node.appendChild(a)))
		}
    },
    
    condition : function(node,a){
		if(a=this.getAttr(node,'condition')){
			if(node.parent()){
				!this.tales(a) && node.parent().replace(node.childIndex(),'')
			} else {
				return '';
			}
		}
	},
	
	define : function(node,a){
		var vars = {};
		if(a=this.getAttr(node,'define')){
			a = a.split(';')
			a = (a instanceof Array) ? a : [a]
			var globalonly = true;
			
			for(var i=a.length-1, j=0;i>=j;i--){
				var atr = a[i].replace(/^\s/,'').replace(/\s$/,'').split(' ');
				if(atr[0]=='global'){
					atr.shift();
					this.globalScope[atr.shift()] = this.tales(atr.join(' '));
				}
				else {
					globalonly = false;
					vars[atr.shift()] = ' string:'+this.tales(atr.join(' '));
				}
			}
			if(!globalonly){
				var replaceAttrs = function(child){
					var attrs = ['content','condition','replace','attributes'];
					for(var x in vars){
						var R = new RegExp("^\\s*"+x+"(?=\\W*)|\\W*"+x+"(?=\\W*)","g");
						for(var i=0,j=attrs.length;i<j;i++){
							if(child.@tal::[attrs[i]].toString().match(R) ){
								child.@tal::[attrs[i]]=child.@tal::[attrs[i]].toString().replace(R,vars[x]);
							}
						}
					}
				}
				replaceAttrs(node);
				for each(child in node..*){
					replaceAttrs(child);					
				}
			}
		}
	},
	
	omit : function(node,a){
		if(a=this.getAttr(node,'omit-tag')){
			if(a==='' || this.tales(a))
			node.parent().replace(node.childIndex(),node.children())
		}
	},
	
	replace : function(node,a){
		if(a=this.getAttr(node,'replace')){
			var newContent = this.tales(a);
			if(newContent=='default')newContent=node.children();
			(newContent && node.parent().replace(node.childIndex(), newContent))
			|| node.parent().replace(node.childIndex(),'')
		}
	},
	
	repeat : function(node,a){
		if(a=this.getAttr(node,'repeat')){
			var tal = this.talNS;
			var item = a.split(' ')[0];
			var dataSet = a.split(' ')[1];
			
			var hasAttr = function(node, talattr){
				return (node.@tal::[talattr].toString().indexOf(item)>-1) 
			}
			
			var replaceAttrs = function(elm,nr){
				var attrs = ['condition','content','replace','attributes'];
				var iterator = 'repeat/'+item+'/';
				for(var i=0;i<attrs.length;i++){
					if(hasAttr(elm,attrs[i])){ 
						var attr = elm.@tal::[attrs[i]].toString();
						for(var j in iterators){attr=attr.replace(iterator+j,iterators[j])}
						elm.@tal::[attrs[i]]=attr.replace(item,(dataSet+'["'+nr+'"]'))
					}
				}
			}
			var iterators = {
				'start' : '0','index' : "0",'number' : 1,'even' : false,'odd' : false,'length' : 0,'end' : '0','key':'0'
			}
			var dataObj = eval(dataSet);
			for(var i in dataObj){iterators.length++}
			var i=0;
			for(var x in dataObj){
				with(iterators){
					start=i==0?true:false;index=i+'';number=(i*1)+1;even=i%2?false:true;odd=!even;end=i==length-1?true:false;key='string:'+x
				}
				var child = node.copy();
				replaceAttrs(child, x)
				for each(n in child..*){
					replaceAttrs(n, x)
				}
				i++;
				node.parent().insertChildBefore(node, child);
			}
			node.parent().replace(node.childIndex(),'')
			
		}
	},
	
	getmacro : function( sourceTpl, macroName, fullpath ){
		if( !this.macros[ fullpath ] ){
			var metal = this.metalNS;
			if(typeof sourceTpl == 'string' && typeof JSTALUtils != 'undefined'){
				sourceTpl = this.loadTpl( JSTALUtils.loadMacro( filename ) );
			}
			var node = ( sourceTpl )..*.( function::attribute( new QName(metal, 'define-macro')).toString() == macroName ) || '';
			this.macros[ fullpath ] = node[0];
			if(sourceTpl===this.tpl)node[0].parent().replace( node[0].childIndex(), '' );
		}
		return this.macros[ fullpath ];
	},
		
	usemacro : function(node, a){
		if(a=this.getMetalAttr(node,'use-macro')){
			var metal = this.metalNS, tpl, macroName, filename, macroXML;
			if( (/^template(\/|\.).*$/m).test(a) ){
				macroName = a.replace(/^template(\/|\.)/m, '');
				tpl = this.tpl;
			} else {
				tpl = a.replace(/(\/)[^\/]+$/g, "")
				macroName = a.replace( filename+'/', '');
			}
			macroXML = new XML( this.getmacro( tpl, macroName, a ) );
			delete macroXML.@metal::['define-macro'];
			
			var targetSlots = ( macroXML )..*.( function::attribute( new QName(metal, 'define-slot')).toString().length )
			
			for each(var i in targetSlots){
				var sourceSlot = ( node )..*.( function::attribute( new QName(metal, 'fill-slot')).toString() == i.@metal::['define-slot'] );
				if(sourceSlot){
					delete sourceSlot.@metal::['fill-slot'];
					delete i.@metal::['define-slot'];
					i.parent().replace( i.childIndex(), sourceSlot )
				}
			}
			node.parent().replace( node.childIndex(), macroXML );
		}
	},
	
	emptynodes : function(node,a){
		if( node.children().length() == 0 && (this.emptyHtmlElements.indexOf(node.localName()) < 0) ) node.appendChild(' ');
	}

};
